﻿Imports System
Imports System.Collections.Generic
Imports System.Diagnostics
Imports System.Runtime.InteropServices
Imports System.Workflow.ComponentModel
Imports System.Workflow.Runtime.Tracking

Namespace System.Workflow.Runtime
    Friend Class TrackingListener
        ' Methods
        Protected Sub New()
        End Sub

        Friend Sub New(ByVal factory As TrackingListenerFactory, ByVal sked As Activity, ByVal exec As WorkflowExecutor, ByVal channels As List([Of] TrackingChannelWrapper), ByVal broker As TrackingListenerBroker, ByVal load As Boolean)
            If ((sked Is Nothing) OrElse (broker Is Nothing)) Then
                WorkflowTrace.Tracking.TraceEvent(TraceEventType.Error, 0, ExecutionStringManager.NullParameters)
            Else
                Me._factory = factory
                Me._channels = channels
                Me._broker = broker
                Me._broker.TrackingListener = Me
            End If
        End Sub

        Friend Sub ActivityStatusChange(ByVal sender As Object, ByVal e As ActivityStatusChangeEventArgs)
            WorkflowTrace.Tracking.TraceInformation("TrackingListener::ActivityStatusChange - Received Activity Status Change Event for activity {0}", New Object() { e.Activity.QualifiedName })
            If (sender Is Nothing) Then
                Throw New ArgumentNullException("sender")
            End If
            If Not GetType(WorkflowExecutor).IsInstanceOfType(sender) Then
                Throw New ArgumentException("sender")
            End If
            If (e Is Nothing) Then
                Throw New ArgumentNullException("e")
            End If
            Dim exec As WorkflowExecutor = DirectCast(sender, WorkflowExecutor)
            If ((Me._channels Is Nothing) OrElse (Me._channels.Count <= 0)) Then
                WorkflowTrace.Tracking.TraceEvent(TraceEventType.Error, 0, ExecutionStringManager.NoChannels)
            Else
                Dim activity As Activity = e.Activity
                If Me.SubscriptionRequired(activity, exec) Then
                    Dim empty As Guid = Guid.Empty
                    Dim contextGuid As Guid = Guid.Empty
                    Me.GetContext(activity, exec, contextGuid, empty)
                    Dim utcNow As DateTime = DateTime.UtcNow
                    Dim nextEventOrderId As Integer = Me._broker.GetNextEventOrderId
                    Dim wrapper As TrackingChannelWrapper
                    For Each wrapper In Me._channels
                        Dim record As New ActivityTrackingRecord(activity.GetType, activity.QualifiedName, contextGuid, empty, activity.ExecutionStatus, utcNow, nextEventOrderId, Nothing)
                        If wrapper.GetTrackingProfile(exec).TryTrackActivityEvent(activity, activity.ExecutionStatus, exec, record) Then
                            wrapper.TrackingChannel.Send(record)
                        End If
                    Next
                End If
            End If
        End Sub

        Friend Sub DynamicUpdateBegin(ByVal sender As Object, ByVal e As DynamicUpdateEventArgs)
            If (sender Is Nothing) Then
                Throw New ArgumentNullException("sender")
            End If
            If Not GetType(WorkflowExecutor).IsInstanceOfType(sender) Then
                Throw New ArgumentException("sender")
            End If
            Dim exec As WorkflowExecutor = DirectCast(sender, WorkflowExecutor)
            If (Not e.ChangeActions Is Nothing) Then
                Me.MakeProfilesPrivate(exec)
                Dim wrapper As TrackingChannelWrapper
                For Each wrapper In Me._channels
                    wrapper.GetTrackingProfile(exec).WorkflowChangeBegin(e.ChangeActions)
                Next
            End If
        End Sub

        Friend Sub DynamicUpdateCommit(ByVal sender As Object, ByVal e As DynamicUpdateEventArgs)
            If (sender Is Nothing) Then
                Throw New ArgumentNullException("sender")
            End If
            If Not GetType(WorkflowExecutor).IsInstanceOfType(sender) Then
                Throw New ArgumentException("sender")
            End If
            Dim skedExec As WorkflowExecutor = DirectCast(sender, WorkflowExecutor)
            Dim utcNow As DateTime = DateTime.UtcNow
            Dim wrapper As TrackingChannelWrapper
            For Each wrapper In Me._channels
                wrapper.GetTrackingProfile(skedExec).WorkflowChangeCommit
            Next
            Dim nextEventOrderId As Integer = Me._broker.GetNextEventOrderId
            Dim wrapper2 As TrackingChannelWrapper
            For Each wrapper2 In Me._channels
                Dim record As New WorkflowTrackingRecord(TrackingWorkflowEvent.Changed, utcNow, nextEventOrderId, New TrackingWorkflowChangedEventArgs(e.ChangeActions, skedExec.WorkflowDefinition))
                If wrapper2.GetTrackingProfile(skedExec).TryTrackInstanceEvent(TrackingWorkflowEvent.Changed, record) Then
                    wrapper2.TrackingChannel.Send(record)
                End If
            Next
        End Sub

        Friend Sub DynamicUpdateRollback(ByVal sender As Object, ByVal e As DynamicUpdateEventArgs)
            If (sender Is Nothing) Then
                Throw New ArgumentNullException("sender")
            End If
            If Not GetType(WorkflowExecutor).IsInstanceOfType(sender) Then
                Throw New ArgumentException("sender")
            End If
            Dim skedExec As WorkflowExecutor = DirectCast(sender, WorkflowExecutor)
            Dim wrapper As TrackingChannelWrapper
            For Each wrapper In Me._channels
                wrapper.GetTrackingProfile(skedExec).WorkflowChangeRollback
            Next
        End Sub

        Private Sub GetContext(ByVal activity As Activity, ByVal exec As WorkflowExecutor, <Out> ByRef contextGuid As Guid, <Out> ByRef parentContextGuid As Guid)
            contextGuid = Me._factory.GetContext(activity)
            If (Not activity.Parent Is Nothing) Then
                parentContextGuid = Me._factory.GetContext(activity.Parent)
            Else
                parentContextGuid = contextGuid
            End If
        End Sub

        Private Sub MakeProfilesPrivate(ByVal exec As WorkflowExecutor)
            Dim wrapper As TrackingChannelWrapper
            For Each wrapper In Me._channels
                wrapper.MakeProfilePrivate(exec)
                Me._broker.MakeProfilePrivate(wrapper.TrackingServiceType)
            Next
        End Sub

        Private Sub NotifyChannels(ByVal evt As TrackingWorkflowEvent, ByVal e As WorkflowExecutionEventArgs, ByVal exec As WorkflowExecutor)
            Dim utcNow As DateTime = DateTime.UtcNow
            Dim nextEventOrderId As Integer = Me._broker.GetNextEventOrderId
            Dim wrapper As TrackingChannelWrapper
            For Each wrapper In Me._channels
                Dim args2 As WorkflowExecutionTerminatingEventArgs
                Dim record As WorkflowTrackingRecord
                Dim eventArgs As EventArgs = Nothing
                Select Case evt
                    Case TrackingWorkflowEvent.Exception
                        Dim args3 As WorkflowExecutionExceptionEventArgs = DirectCast(e, WorkflowExecutionExceptionEventArgs)
                        eventArgs = New TrackingWorkflowExceptionEventArgs(args3.Exception, args3.CurrentPath, args3.OriginalPath, args3.ContextGuid, args3.ParentContextGuid)
                        goto Label_00BC
                    Case TrackingWorkflowEvent.Terminated
                        args2 = DirectCast(e, WorkflowExecutionTerminatingEventArgs)
                        If (args2.Exception Is Nothing) Then
                            Exit Select
                        End If
                        eventArgs = New TrackingWorkflowTerminatedEventArgs(args2.Exception)
                        goto Label_00BC
                    Case TrackingWorkflowEvent.Suspended
                        eventArgs = New TrackingWorkflowSuspendedEventArgs(DirectCast(e, WorkflowExecutionSuspendingEventArgs).Error)
                        goto Label_00BC
                    Case Else
                        goto Label_00BC
                End Select
                eventArgs = New TrackingWorkflowTerminatedEventArgs(args2.Error)
            Label_00BC:
                record = New WorkflowTrackingRecord(evt, utcNow, nextEventOrderId, eventArgs)
                If wrapper.GetTrackingProfile(exec).TryTrackInstanceEvent(evt, record) Then
                    wrapper.TrackingChannel.Send(record)
                End If
            Next
        End Sub

        Private Sub NotifyChannelsOfCompletionOrTermination()
            Dim wrapper As TrackingChannelWrapper
            For Each wrapper In Me._channels
                wrapper.TrackingChannel.InstanceCompletedOrTerminated
            Next
        End Sub

        Friend Sub ReloadProfiles(ByVal exec As WorkflowExecutor, ByVal instanceId As Guid)
            Me._factory.ReloadProfiles(exec, instanceId, (Me._broker), (Me._channels))
        End Sub

        Private Function SubscriptionRequired(ByVal activity As Activity, ByVal exec As WorkflowExecutor) As Boolean
            Dim flag As Boolean = False
            Dim wrapper As TrackingChannelWrapper
            For Each wrapper In Me._channels
                If (wrapper.GetTrackingProfile(exec).ActivitySubscriptionNeeded(activity) AndAlso Not flag) Then
                    flag = True
                End If
            Next
            Return flag
        End Function

        Friend Sub UserTrackPoint(ByVal sender As Object, ByVal e As UserTrackPointEventArgs)
            Dim guid As Guid
            Dim guid2 As Guid
            If Not GetType(WorkflowExecutor).IsInstanceOfType(sender) Then
                Throw New ArgumentException("sender is not WorkflowExecutor")
            End If
            Dim exec As WorkflowExecutor = DirectCast(sender, WorkflowExecutor)
            Dim activity As Activity = e.Activity
            Dim utcNow As DateTime = DateTime.UtcNow
            Dim nextEventOrderId As Integer = Me._broker.GetNextEventOrderId
            Me.GetContext(activity, exec, guid2, guid)
            Dim wrapper As TrackingChannelWrapper
            For Each wrapper In Me._channels
                Dim record As New UserTrackingRecord(activity.GetType, activity.QualifiedName, guid2, guid, utcNow, nextEventOrderId, e.Key, e.Args)
                If wrapper.GetTrackingProfile(exec).TryTrackUserEvent(activity, e.Key, e.Args, exec, record) Then
                    wrapper.TrackingChannel.Send(record)
                End If
            Next
        End Sub

        Friend Sub WorkflowExecutionEvent(ByVal sender As Object, ByVal e As WorkflowExecutionEventArgs)
            If (sender Is Nothing) Then
                Throw New ArgumentNullException("sender")
            End If
            Dim exec As WorkflowExecutor = [TryCast](sender,WorkflowExecutor)
            If (exec Is Nothing) Then
                Throw New ArgumentException(ExecutionStringManager.InvalidSenderWorkflowExecutor)
            End If
            Select Case e.EventType
                Case WorkflowEventInternal.Completing
                    Me.NotifyChannels(TrackingWorkflowEvent.Completed, e, exec)
                    Me.NotifyChannelsOfCompletionOrTermination
                    Return
                Case WorkflowEventInternal.Completed, WorkflowEventInternal.Idle, WorkflowEventInternal.Suspended, WorkflowEventInternal.Resumed, WorkflowEventInternal.Persisted, WorkflowEventInternal.Unloaded, WorkflowEventInternal.Loaded, WorkflowEventInternal.Terminated, WorkflowEventInternal.Aborted, WorkflowEventInternal.Runnable, WorkflowEventInternal.Executing, WorkflowEventInternal.NotExecuting, WorkflowEventInternal.ActivityStateCreated, WorkflowEventInternal.HandlerEntered, WorkflowEventInternal.HandlerExited
                    Exit Select
                Case WorkflowEventInternal.SchedulerEmpty
                    Me.NotifyChannels(TrackingWorkflowEvent.Idle, e, exec)
                    Return
                Case WorkflowEventInternal.Suspending
                    Me.NotifyChannels(TrackingWorkflowEvent.Suspended, e, exec)
                    Return
                Case WorkflowEventInternal.Resuming
                    Me.NotifyChannels(TrackingWorkflowEvent.Resumed, e, exec)
                    Return
                Case WorkflowEventInternal.Persisting
                    Me.NotifyChannels(TrackingWorkflowEvent.Persisted, e, exec)
                    Return
                Case WorkflowEventInternal.Unloading
                    Me.NotifyChannels(TrackingWorkflowEvent.Unloaded, e, exec)
                    Return
                Case WorkflowEventInternal.Exception
                    Me.NotifyChannels(TrackingWorkflowEvent.Exception, e, exec)
                    Return
                Case WorkflowEventInternal.Terminating
                    Me.NotifyChannels(TrackingWorkflowEvent.Terminated, e, exec)
                    Me.NotifyChannelsOfCompletionOrTermination
                    Return
                Case WorkflowEventInternal.Aborting
                    Me.NotifyChannels(TrackingWorkflowEvent.Aborted, e, exec)
                    Return
                Case WorkflowEventInternal.UserTrackPoint
                    Me.UserTrackPoint(exec, DirectCast(e, UserTrackPointEventArgs))
                    Return
                Case WorkflowEventInternal.ActivityStatusChange
                    Me.ActivityStatusChange(exec, DirectCast(e, ActivityStatusChangeEventArgs))
                    Return
                Case WorkflowEventInternal.DynamicChangeBegin
                    Me.DynamicUpdateBegin(exec, DirectCast(e, DynamicUpdateEventArgs))
                    Return
                Case WorkflowEventInternal.DynamicChangeRollback
                    Me.DynamicUpdateRollback(exec, DirectCast(e, DynamicUpdateEventArgs))
                    Return
                Case WorkflowEventInternal.DynamicChangeCommit
                    Me.DynamicUpdateCommit(exec, DirectCast(e, DynamicUpdateEventArgs))
                    Exit Select
                Case WorkflowEventInternal.Creating
                    Me.NotifyChannels(TrackingWorkflowEvent.Created, e, exec)
                    Return
                Case WorkflowEventInternal.Starting
                    Me.NotifyChannels(TrackingWorkflowEvent.Started, e, exec)
                    Return
                Case WorkflowEventInternal.Loading
                    Me.NotifyChannels(TrackingWorkflowEvent.Loaded, e, exec)
                    Return
                Case Else
                    Return
            End Select
        End Sub


        ' Properties
        Friend Property Broker As TrackingListenerBroker
            Get
                Return Me._broker
            End Get
            Set(ByVal value As TrackingListenerBroker)
                Me._broker = value
            End Set
        End Property


        ' Fields
        Private _broker As TrackingListenerBroker
        Private _channels As List([Of] TrackingChannelWrapper)
        Private _factory As TrackingListenerFactory
    End Class
End Namespace

